Skip to content

Motion Accessibility

In general, I believe that motion is an important part of any web application. It can provide a sense of continuity, making the UI feel more tangible. It also provides an opportunity for us to surprise users with an unexpected bit of whimsy. ✨

But not everyone experiences motion the same way. For some, motion can trigger physical symptoms like vertigo, nausea, and malaise.

Operating systems provide a setting users can toggle, in order to disable potentially-upsetting animations:

macOS system preferences showing the “Reduce Motion” setting

On macOS, you can find this setting under Accessibility » Display. On Windows, it's under Accessibility » Visual Effects. You can also emulate this setting in-browser; more about this shortly.

There's a bit of a good-news-bad-news situation with this setting:

  • The bad news is that this setting has no effect on web animations. It only disables motion within the operating system itself (eg. minimizing windows).
  • The good news is that we have access to this setting on the web, in the form of a media query!

When working in CSS, we can apply transitions / keyframe animations only for users who haven't opted out:

@media (prefers-reduced-motion: no-preference) {
.some-elem {
animation: swoop 700ms;
}
.some-other-elem {
transition: transform 300ms;
}
}

When it comes to Framer Motion, we can tell it to respect user preferences with a special component, MotionConfig. Here's how it works:

import React from 'react';
import { MotionConfig } from 'framer-motion';
function App() {
return (
<MotionConfig reducedMotion="user">
{/* The entire application here */}
</MotionConfig>
);
}
export default App;

By setting reducedMotion to "user", we're telling Framer Motion to obey the prefers-reduced-motion media query. If the user toggles the “Reduce motion” setting, all animations will be disabled, and elements will instantly teleport to their new positions instead of smoothly gliding.

I wish Framer Motion defaulted to this setting. It makes a lot more sense to me. But, since it doesn't, we need to set it ourselves.

I recommend wrapping this component around our entire application. In the next lesson, we'll cover how to set this up in Next 13 applications.

Emulating prefers-reduced-motion

Every now and then, it's worth testing our applications with the “Reduce motion” setting enabled. It's important to make sure our application is still clear and easy-to-use even with animations disabled.

Rather than fussing with the “System Preferences” in your OS, we can emulate this setting within the browser.

In Chrome, you can toggle this setting by enabling the devtools, opening the Command Palette (Ctrl + Shift + P), and typing “reduce”:

Screenshot of the Chrome devtools, showing the option to emulate prefers-reduced-motion

For other browsers, you can google “emulate prefers-reduced-motion” to find specific instructions.

Let's give it a shot. Below, you'll find our Toggle component from earlier, using the MotionConfig component to respect the user's preference. Your mission is to emulate “Reduce motion” in the browser devtools, to see what the experience is like for folks who disable motion.

Note: You'll need to refresh the iframe using the button after enabling the emulation.

Code Playground

import React from 'react';
import { MotionConfig } from 'framer-motion';

import Toggle from './Toggle';

function App() {
const [isEnabled, setIsEnabled] = React.useState(false);
return (
<MotionConfig reducedMotion="user">
<Toggle
value={isEnabled}
onChange={setIsEnabled}
/>
</MotionConfig>
);
}

export default App;
  1. You have Reduced Motion enabled on your device. Animations may not appear as expected.

By emulating the prefers-reduced-motion media query, the toggle should jump immediately from one side to another:

We'll see MotionConfig again in the final project, but if you'd like to have a bit more practice, I'd suggest going through the previous exercises and adding support for reduced motion.

Going deeper

If you'd like to learn more about this stuff, I have a blog post, “Accessible Animations in React”. We go a bit deeper into why it's so important to be mindful of our animations.

There's also a hook that comes with Framer Motion, useReducedMotion. It can be useful if you'd like to have finer-grained control; instead of disabling animations outright, we can supply alternative animations (eg. using opacity instead of motion). You can learn more in the “useReducedMotion” documentation.

That said, I don't think we need to overcomplicate this. As long as you use the MotionConfig component as described in this lesson, as well as the prefers-reduced-motion media query for any CSS-based motion, you should be good to go. 👍